#include "ItemModel.h"
#include "ItemRoot.h"
#include "ItemGroup.h"
#include "Item.h"
#include "ItemReference.h"
#include "Defines.h"



#include <QDebug>



ItemModel::ItemModel(QObject* parent)
    : QAbstractItemModel(parent)
{
}



ItemModel::ItemModel(ItemRoot *root, QObject *parent)
    : QAbstractItemModel(parent)
{
    setRootItem(root);
}



ItemModel::~ItemModel()
{
}



AbstractItem* ItemModel::indexItem(const QModelIndex& index) const
{
    return dynamic_cast<AbstractItem*>(static_cast<QObject*>(index.internalPointer()));
}



AbstractItem* ItemModel::rootItem() const
{
    return m_root.data();
}



void ItemModel::setRootItem(AbstractItem* root)
{
    emit layoutAboutToBeChanged();
    if (!m_root.isNull())
    {
        disconnect(m_root.data(), SIGNAL(sigDirty()), this, SIGNAL(layoutChanged()));
    }

    m_root = dynamic_cast<ItemRoot*>(root);
    if (!m_root.isNull())  connect(m_root.data(), SIGNAL(sigDirty()), this, SIGNAL(layoutChanged()));

    emit layoutChanged();
}



QModelIndex ItemModel::index(int row, int column, const QModelIndex &parent) const
{
    if (m_root.isNull()) return QModelIndex();
    if (!hasIndex(row, column, parent)) return QModelIndex();

    AbstractItem* parentItem = 0;
    if (!parent.isValid())
    {
        parentItem = m_root.data();
    }
    else
    {
        parentItem = indexItem(parent);
    }

    if (!parentItem || parentItem->itemCount() <= row) return QModelIndex();

    AbstractItem* item = parentItem->itemAt(row);
    if (!item) return QModelIndex();

    return createIndex(row, column, item);
}



QModelIndex ItemModel::parent(const QModelIndex &child) const
{
    if (m_root.isNull() || !child.isValid()) return QModelIndex();

    AbstractItem* childItem = indexItem(child);
    if (!childItem || childItem == rootItem()) return QModelIndex();

    AbstractItem* item = qobject_cast<AbstractItem*>(childItem->parent());
    if (!item || !item->parent() || item == rootItem()) return QModelIndex();


    int row = item->parent()->children().indexOf(item);
    return createIndex(row, 0, item);
}



QModelIndex ItemModel::referencedIndex(const QModelIndex& reference) const
{
    if (!rootItem() || !reference.isValid()) return QModelIndex();

    QModelIndex searchIndex = index(0, AbstractItem::IdData, QModelIndex());
    if (!searchIndex.isValid()) return QModelIndex();

    QModelIndex categoryIndex = reference.sibling(reference.row(), AbstractItem::CategoryData);
    if (!categoryIndex.isValid() || categoryIndex.data(Qt::EditRole).toInt() != ItemCategory_Reference) return QModelIndex();

    QModelIndex referenceIndex = reference.sibling(reference.row(), ItemReference::ReferenceData);
    if (!referenceIndex.isValid()) return QModelIndex();

    QModelIndexList foundIndexes = match(searchIndex,
                                         Qt::EditRole,
                                         referenceIndex.data(Qt::EditRole),
                                         1,
                                         Qt::MatchExactly | Qt::MatchRecursive);

    if (foundIndexes.isEmpty()) return QModelIndex();

    return foundIndexes.first();

#if 0
    for (int group = 0; group < rowCount(QModelIndex()); group++)
    {
        QModelIndex groupIndex = index(group, AbstractItem::IdData, QModelIndex());
        QString groupId = groupIndex.data(Qt::EditRole).toString();

        if (refId == groupId) return groupIndex;
        if (!refId.contains(groupId)) continue;

        if (!hasChildren(groupIndex)) return QModelIndex();

        for (int item = 0; item < rowCount(groupIndex); item++)
        {
            QModelIndex itemIndex = index(item, AbstractItem::IdData, groupIndex);
            QString itemId = itemIndex.data(Qt::EditRole).toString();
            if (refId != itemId) continue;

            return itemIndex;
        }
    }

    return QModelIndex();
#endif
}


#if 0
QModelIndexList ItemModel::referencedIndexes(const QModelIndex& index) const
{
    QModelIndex group = reference(ref);
    if (!group.isValid()) return QModelIndexList();

    QModelIndexList refs;
    for (int item = 0; item < rowCount(group); item++)
    {
        QModelIndex itemIndex = index(item, AbstractItem::IdData, group);
        if (!itemIndex.isValid()) continue;

        refs.append(itemIndex);
    }

    return refs;

}
#endif



int ItemModel::rowCount(const QModelIndex &parent) const
{
    if (m_root.isNull()) return 0;

    AbstractItem* item = 0;

    if (!parent.isValid())
    {
        item = m_root.data();
    }
    else
    {
        item = indexItem(parent);
    }

    if (!item) return 0;

    return item->itemCount();
}



int ItemModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);

    if (m_root.isNull()) return 0;

    int ret = 0;
    foreach (AbstractItem* item, m_root->findChildren<AbstractItem*>())
    {
        if (!item) continue;
        if (item->fieldCount() > ret) ret = item->fieldCount();
    }

    return ret;
}



QVariant ItemModel::data(const QModelIndex &index, int role) const
{
    if (role != Qt::DisplayRole && role != Qt::EditRole) return QVariant();
    if (m_root.isNull() || !index.isValid()) return QVariant();

    AbstractItem* item = indexItem(index);

    if (!item || item == rootItem()) return QVariant();

    return item->data(index.column(), role);

}



bool ItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid() || role != Qt::EditRole) return false;

    AbstractItem* item = indexItem(index);
    if (!item) return false;

    //emit layoutAboutToBeChanged();
    item->setData(index.column(), value);
    emit dataChanged(index, index);
    //emit layoutChanged();
    return true;
}



QVariant ItemModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    Q_UNUSED(orientation);

    if (role != Qt::DisplayRole) return QVariant();

    if (section < 0 || section >= columnCount(QModelIndex())) return QVariant();

    // todo: all data type names to common defines.
    switch (section)
    {
    case AbstractItem::IdData:          return tr("Id");
    case AbstractItem::NameData:        return tr("Name");
    case AbstractItem::DescData:        return tr("Description");
    case AbstractItem::CategoryData:    return tr("Category");

    case Item::ValueData:               return tr("Value");
    case ItemReference::TypeData:       return tr("Reference Type");
    case ItemReference::ReferenceData:        return tr("Referenced Item");
    default:                            return tr("Unknown Field");
    }
}



Qt::ItemFlags ItemModel::flags(const QModelIndex &index) const
{
    switch (index.column())
    {
    case AbstractItem::IdData: return QAbstractItemModel::flags(index);
    case AbstractItem::CategoryData: return QAbstractItemModel::flags(index);
    }

    AbstractItem* item = indexItem(index);
    if (item->rootItem() != rootItem()) return QAbstractItemModel::flags(index);

    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}


bool ItemModel::insertRows(int row, int count, const QModelIndex &parent)
{
    Q_UNUSED(count);
    return insertRow(row,parent);
}


bool ItemModel::removeRows(int row, int count, const QModelIndex &parent)
{
    Q_UNUSED(count);
    return removeRow(row, parent);
}

bool ItemModel::insertRow(int row, const QModelIndex &parent)
{
    if (m_root.isNull())
    {
        qDebug("Root is null");
        return false;
    }

    if (!parent.isValid())
    {
        emit layoutAboutToBeChanged();
        beginInsertRows(parent, row, row);
        (void) new ItemGroup(tr("New Item Group"), m_root.data());
        endInsertRows();
        emit layoutChanged();
        return true;
    }

    AbstractItem* parentItem = indexItem(parent);
    if (!parentItem)
    {
        qDebug("Parent is null");
        return false;
    }

    switch (parentItem->category())
    {
    case ItemCategory_Group:
        emit layoutAboutToBeChanged();
        beginInsertRows(parent, row, row);
        (void) new Item(tr("New Item"), parentItem);
        endInsertRows();
        emit layoutChanged();

        break;
    case ItemCategory_Item:
        emit layoutAboutToBeChanged();
        beginInsertRows(parent, row, row);
        (void) new ItemReference(parentItem);
        endInsertRows();
        emit layoutChanged();
        break;
    default:
        return false;
    }

    return true;
}



bool ItemModel::removeRow(int row, const QModelIndex &parent)
{
    if (m_root.isNull()) return false;

    AbstractItem* item = 0;
    if (!parent.isValid())
    {
        item = m_root.data();
    }
    else
    {
        item = indexItem(parent);
    }

    if (!item) return false;

    // todo: remove layout change signals and create slot for rowsAboutToBeRemoved() signal.
    emit layoutAboutToBeChanged();
    beginRemoveRows(parent, row, row);
    item->removeAt(row);
    endRemoveRows();
    emit layoutChanged();


    return true;
}



//QVariant ItemModel::siblingData(const int& col, const QModelIndex& index, const int& role) const
//{
//    if (!index.isValid()) return QVariant();

//    QModelIndex sibling = index.sibling(index.row(), col);
//    if (!sibling.isValid()) return QVariant();

//    return sibling.data(role);
//}
